package com.zlf.sshtools.portforward;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.ssh.JschUtil;
import cn.hutool.log.Log;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Proxy;
import com.jcraft.jsch.ProxySOCKS5;
import com.jcraft.jsch.Session;

/**
 * 端口转发，创建回话后会启动一个线程，每10秒发送一条保持回话的消息（如果发送失败，会自动重新连接），保证连接不会中断。
 *
 * @author zlf
 */
public class PortforwardChannel {

    private static final Log LOG = Log.get();

    private Session session;
    private int localPort = 18922;
    private String remoteHost = "20.1.8.176";
    private int remotePort = 18922;
    private String sshUser = "ossres";
    private String sshPassword = "gkh58Efs!ke";
    private String sshHost = "135.224.84.157";
    private int sshPort = 22;
    private String socksProxy;
    private Thread monitThread;

    public PortforwardChannel(PortforwardVO vo) {
        this(vo.localPort, vo.remotePort, vo.remoteHost, vo.sshHost, vo.sshUser, vo.sshPassword, vo.socksProxy);
    }

    /**
     * 实例化一个端口转发通道
     *
     * @param localPort
     * @param remotePort
     * @param remoteHost
     * @param sshHost
     * @param sshUser
     * @param sshPassword
     */
    public PortforwardChannel(int localPort, int remotePort, String remoteHost, String sshHost, String sshUser, String sshPassword, String socksProxy) {
        this.localPort = localPort;
        this.remotePort = remotePort;
        this.remoteHost = remoteHost;
        String[] arr = sshHost.split(":");
        this.sshHost = arr[0];
        if (arr.length > 1) {
            this.sshPort = Integer.parseInt(arr[1]);
        }
        this.sshUser = sshUser;
        this.sshPassword = sshPassword;
        this.socksProxy = socksProxy;
    }

    public void start() throws JSchException {
        connect();
        monit();
    }

    public void stop() {
        if (monitThread != null) {
            monitThread.interrupt();
        }
    }

    private void connect() throws JSchException {
        session = JschUtil.createSession(sshHost, sshPort, sshUser, sshPassword);//直接getSession的话，会直接连接，无法使用代理了
        if (StrUtil.isNotBlank(socksProxy)) {
            String arr[] = socksProxy.split(":");
            Proxy proxy = new ProxySOCKS5(arr[0], Integer.parseInt(arr[1]));
            session.setProxy(proxy);
        }
        session.connect(10000);
        if (JschUtil.bindPort(session, remoteHost, remotePort, localPort)) {
            LOG.info("port forward success,remoteHos:{},remotePort:{},localPort:{}", remoteHost, remotePort, localPort);
        } else {
            LOG.warn("port forward fail,remoteHos:{},remotePort:{},localPort:{}", remoteHost, remotePort, localPort);
        }
    }

    private void monit() {
        monitThread = new Thread(new Runnable() {
            public void run() {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) {
                        break;
                    }
                    try {
                        session.sendKeepAliveMsg();
                    } catch (Exception e) {
                        LOG.warn(e, "send keepAliveMsg fail,will reconnect . remoteHost:{},remotePort:{},localPort:{}", remoteHost, remotePort, localPort);
                        try {
                            connect();
                        } catch (JSchException ex) {
                            LOG.warn(ex, "reconnect fail. remoteHost:{},remotePort:{},localPort:{}", remoteHost, remotePort, localPort);
                        }
                    } finally {
                        if (!ThreadUtil.sleep(10000)) {
                            break;
                        }
                    }
                }
                LOG.warn("port forward stop,remoteHos:{},remotePort:{},localPort:{}", remoteHost, remotePort, localPort);
                JschUtil.close(session);
            }
        });
        monitThread.start();
        LOG.info("portforward monit thread start. remoteHost:{},remotePort:{},localPort:{}", remoteHost, remotePort, localPort);
    }
}
